Chatter Box 2
Volume Number: 5
Issue Number: 12
Column Tag: Intermediate Mac'ing
AppleTalk Chatter Box 
By Rob Hafernik, Austin, TX
Note: Source code files accompanying article are located on MacTech CD-ROM orsource code disks.
A Chat Program for AppleTalk
(Rob Hafernik has been programming at one job or another for 10 years. Over
the years, he’s been involved in a wide variety of programming projects; working with
banks, hospitals, the Space Shuttle, robotics, graphics, and so on. He’s finally lucked
out and now actually gets paid to program the Macintosh for Technology Works in
Austin, Texas. He can be reached via e-mail on BIX or MacNet as user ID “rob42” )
From the first time I sat down to a Mac connected to AppleTalk, I wanted to write
a program to let users on AppleTalk-connected Macs chat back and forth. The idea
languished in the back of my mind for years until I recently found myself with a need
to learn AppleTalk and a whole day free of other programming responsibilities. Thus
Chatterbox was begun.
I had already spent a couple of hours studying Inside AppleTalk to get a feel for
the various AppleTalk protocols and more time pouring over the various Inside
Macintosh chapters that cover AppleTalk. From the start, I’d decided that the program
would not be restricted to just two users talking back and forth. The program had to be
able to let any user send a message to any subset of the rest of the users on the internet
(or all of them). I also wanted to keep the program as simple and fool-proof as
possible. From all of this I came to the decision that Chatterbox would use the Name
Binding Protocol (NBP) to see who was available to talk to and the Datagram Delivery
Protocol (DDP) to send and receive the actual messages.
AppleTalk Basics
First a few AppleTalk terms. Each device (such as your Mac or LaserWriter) on
an AppleTalk network is called a node. Up to 32 devices can be connected together to
form an AppleTalk network (up to 255 devices are allowed with Apple’s new release of
AppleTalk). Several networks may be connected together (via special nodes called
internet routers) to form a zone. The sum of all of the networks connected together are
referred to as an internet. As each device boots up, it establishes contact with its
network and comes up with a unique node id. Since more than one program may be
running on a given node that wants to use the network, an addressing mechanism called
sockets allows each node to have up to 254 separate addressable entities running at any
one time. The low numbered sockets are reserved by Apple for the low-level protocols
(such as the Name Binding Protocol we’ll discuss below), but sockets 128-254 are
open for use by any Mac program. Each socket must have a socket listener. This is a
piece of code that the DDP calls to handle incoming messages. Apple supplies a default
socket listener that does the minimum - it copies incoming messages to the buffer you
supply and sets a result field.
The Datagram Delivery Protocol is the simplest data transfer protocol of them all
and the one that all of the higher-level protocols are based on: it doesn’t guarantee
delivery and it doesn’t guarantee that packets will arrive intact. What does it do?
Well, it provides for the “best effort” delivery of a packet of up to 586 bytes to a
given network, node and socket on the internet. What more could a chat program ask
for? One note: throughout this program I have used the “alternate interface”, as
opposed to the “preferred interface”. The “preferred interface” (described in Inside
Macintosh Volume V) is based on the same type of parameter-block calls as the
low-level file system interface. Unfortunately, since the “preferred interface”
provides no equivalent of the “DDPRead” function, a socket listener and read function
must be provided by the programmer. For this simple application, the “alternate
interface” proved best and easiest to understand.
The Name Binding Protocol provides a way for programs that will use AppleTalk
(client programs) to register on the network. Each client program opens a socket,
then registers a name and type with the NBP. A client program can also ask NBP for
the names, types, and addresses of other clients in its zone. NBP provides a wild-card
match scheme that allows, for example, a client to scan the zone for all other names of
a given type. This is the mechanism that Chatterbox uses to find other users to talk to.
The Chatterbox Program
Now let’s look at the program. When Chatterbox first starts up, it asks the user
for the name they wish to use during the session (figure 1). The name defaults to the
Chooser name, but the user may override this and provide any name less than 32
characters long (the limit allowed by the NBP). After that, the program displays a
window with several parts (figure 2). There is a scrollable area where the names of
other Chatterbox users are displayed; an area for status messages (I like programs
that let me know what they’re doing, e specially ones like this whose actions are mostly
invisible); a TextEdit field for messages you wish to send; and a read-only TextEdit
field where incoming messages are displayed.
Figure 1.
To use the program, the user selects recipients from the list. Multiple selections
are allowed, both continuous selections made with a shift-click and discontinuous
selections made with a command-click. Messages entered in the edit-text field are
transmitted by hitting the key at the end of the message. As others send
messages, they’re displayed in the area at the bottom. As more messages come in, the
old ones scroll up and are eventually deleted as they scroll off the top.
Figure 2.
The flow of the program is pretty simple. Once the usual Mac stuff is initialized,
the program opens a socket that will be used to receive messages. To keep things easy,
I used the default, Apple-supplied, socket listener. Then the RegisterName routine
allocates a parameter block, fills in the user’s name, type, zone, socket and a few other
parameters and calls NBPRegister to do the work. The zone string is set to “*”,
meaning “this zone” and the type string to “Chatterbox”. By using the “*” zone
name, Chatterbox is limiting communications to a single zone (the one it’s in), but
this simplifies the program considerably. Note that when the program quits, it must
call NBPRemove to take this name out of the list - see the UnRegisterName routine.
After registering on the internet, the next thing the program does is send out a
special greeting to all other Chatterboxes. Why? Well, it’s a long story. When I first
wrote the program, I thought that it would periodically scan the zone for new users and
add them to its display. This proved to be very cumbersome (it takes a second or two to
do the lookup for one thing, a pretty big interruption if you’re doing anything) and I
went through several schemes before I came up with another method: as Chatterbox
gets incoming messages, it looks to see if they begin with the non-breaking space
(option-space from the keyboard). If so, it’s a signal that the list of users in the zone
has changed and it’s time to call CheckForUsers (discussed below). This means that the
user is only interrupted when a new user comes along (or goes away, we’ll get to that
later). So this special message that is sent out by a new Chatterbox coming along
triggers all the other Chatterboxes to look around the zone for their fellows. Why go to
all the trouble of a CheckForUsers and not just add the new user to the list? In a word,
redundancy. This may take a little extra processor time, but it guarantees that the list
of users is up-to-date, regardless of the whims and crashes of the network.
The CheckForUsers routine is a bit complicated. The current list of other
Chatterboxes in the zone is displayed using the list manager. Whenever CheckForUsers
scans the network for other users, it must add new Chatterboxes to the list and delete
Chatterboxes no longer active from the list - all without disturbing the user’s
selections in the list. It first calls NBPLookUp to get the names of all Chatterboxes,
then it extracts one name at a time using NBPExtract. Each name is compared to those
in the list; if there’s a match, that entry is marked as “found” and the next name is
processed. If the name is not found in the list, it’s added. After all of the names have
been extracted, any names not marked as “found” are deleted from the list. This would
all be better handled by a custom LDEF, but I’ll save that for another day.
To receive messages with DDP, a program must first ask for them and provide a
buffer to store them in. The AskForMessages routine takes care of this. It’s very
simple, just a single call to DDPRead. In this case, DDPRead is called with the async
parameter set to true, meaning that the program will go on with its business while it
waits for a message to come in. Once DDPRead has been called, it’s the program’s
responsibility to watch the abResult field of the InABRecord parameter block to go to
zero or a negative value. DDPRead sets this field to a positive number - the socket
listener will set it to zero (if everything is OK) or negative (the error code) when a
message comes in. In Chatterbox, the abResult field is examined once each time a null
event is received.
When a message has been detected, the ReadMessage routine is called to pull the
message out of the buffer and display it in the incoming messages field. As soon as the
message is dealt with, ReadMessage calls AskForMessage to get ready for the next
message.
Sending messages is simple too. When the user hits to send a message,
it’s pulled out of the TextEdit field and put into a message buffer. The program then
runs through the user list, building a parameter block for each user selected and
calling DDPWrite to send the message. Sometimes the SendMessage routine must be
called upon to send a message to everyone in the list, even if they aren’t selected (this
happens during the start-up greeting, for example). In this case, the calling routine
sets the toAll parameter true.
When the user quits the program, it again sends out a special message that starts
with a non-breaking space. This time it’s an “adios” message. This message, like the
greeting message, is used to tell all of the other Chatterbox programs in the zone to
re-build their lists of users, since one has just quit.
Extras and Options
That’s the bulk of the program, but there are a few extras. There are two buttons
in the window for frequently-used tasks. The Scan button just looks for Chatterboxes
on the network. The user can press it if things seem a little screwy or out of sync -
it’s really just a security blanket, it’s rarely needed. The Select All button selects all
of the users in the list, something I found most users do often. There are also three
optional behaviors that can be set or unset in the Options menu. The first sets a flag
that tells the program to beep with each incoming message. This is in case a message
comes in when the user isn’t watching or the window is in the background under
MultiFinder. The second option allows the user to see the messages transmitted echoed
in the incoming message field. The last option allows the user to have all new users
that come along be automatically selected as recipients.
What next?
So how could this program be improved? Well, it would be nice if the program
let you pick the zone you wanted to chat in, instead of just letting you chat in your own.
The AppleTalk Zone Information Protocol could be used to get a list of zones on the
internet. It would also be nice if the incoming messages that were deleted from the
TextEdit field as they scrolled off the top could instead be scrolled back down with a
scroll bar. A file transfer facility would be nice. No doubt all of you out there can
come up with more improvements.
As you see, AppleTalk is easy to use. This simple, low-level approach, using DDP
and NBP, could be used to implement games, e-mail, network copy protection schemes
and so on. As more and more Macs are networked out there, network awareness
becomes more and more a must for any application.
Listing: Chatter.c
/* ======== Chatterbox ======= */
/*** defines */
#define NULL 0L
#define MAXUSERS 64
#define BUFSIZE 600
#define NONE 0
#define REGISTER 1
#define WAITING 2
#define SCANNING 3